package pedro.rtsp.rtcp

import android.util.Log
import pedro.rtsp.rtsp.Protocol
import pedro.rtsp.rtsp.RtpFrame
import pedro.rtsp.utils.setLong
import java.io.IOException
import java.io.OutputStream
import java.util.*
import kotlin.jvm.Throws

/**
 * Created by pedro on 7/11/18.
 */
internal abstract class BaseSenderReport internal constructor() {

    private val interval: Long = 3000
    private val MTU = 1500
    private val videoBuffer = ByteArray(MTU)
    private val audioBuffer = ByteArray(MTU)
    private var videoTime: Long = 0
    private var audioTime: Long = 0
    private var videoPacketCount = 0
    private var videoOctetCount = 0
    private var audioPacketCount = 0
    private var audioOctetCount = 0
    protected val PACKET_LENGTH = 28
    protected val TAG = "BaseSenderReport"

    companion object {
        @JvmStatic
        fun getInstance(
            protocol: Protocol,
            videoSourcePort: Int,
            audioSourcePort: Int
        ): BaseSenderReport {
            return if (protocol === Protocol.TCP) {
                SenderReportTcp()
            } else {
                SenderReportUdp(videoSourcePort, audioSourcePort)
            }
        }
    }

    init {
        /*							     Version(2)  Padding(0)					 					*/
        /*									 ^		  ^			PT = 0	    						*/
        /*									 |		  |				^								*/
        /*									 | --------			 	|								*/
        /*									 | |---------------------								*/
        /*									 | ||													*/
        /*									 | ||													*/
        videoBuffer[0] = "10000000".toInt(2).toByte()
        audioBuffer[0] = "10000000".toInt(2).toByte()

        /* Packet Type PT */videoBuffer[1] = 200.toByte()
        audioBuffer[1] = 200.toByte()

        /* Byte 2,3          ->  Length		                     */
        videoBuffer.setLong(PACKET_LENGTH / 4 - 1.toLong(), 2, 4)
        audioBuffer.setLong(PACKET_LENGTH / 4 - 1.toLong(), 2, 4)
        /* Byte 4,5,6,7      ->  SSRC                            */
        videoBuffer.setLong(Random().nextInt().toLong(), 4, 8)
        audioBuffer.setLong(Random().nextInt().toLong(), 4, 8)
        /* Byte 8,9,10,11    ->  NTP timestamp hb				 */
        /* Byte 12,13,14,15  ->  NTP timestamp lb				 */
        /* Byte 16,17,18,19  ->  RTP timestamp		             */
        /* Byte 20,21,22,23  ->  packet count				 	 */
        /* Byte 24,25,26,27  ->  octet count			         */
    }

    abstract fun setDataStream(outputStream: OutputStream, host: String)

    fun update(rtpFrame: RtpFrame, isEnableLogs: Boolean) {
        if (rtpFrame.channelIdentifier == 2.toByte()) {
            updateVideo(rtpFrame, isEnableLogs)
        } else {
            updateAudio(rtpFrame, isEnableLogs)
        }
    }

    @Throws(IOException::class)
    abstract fun sendReport(
        buffer: ByteArray,
        rtpFrame: RtpFrame,
        type: String,
        packetCount: Int,
        octetCount: Int,
        isEnableLogs: Boolean
    )

    private fun updateVideo(rtpFrame: RtpFrame, isEnableLogs: Boolean) {
        videoPacketCount++
        videoOctetCount += rtpFrame.length
        videoBuffer.setLong(videoPacketCount.toLong(), 20, 24)
        videoBuffer.setLong(videoOctetCount.toLong(), 24, 28)
        if (System.currentTimeMillis() - videoTime >= interval) {
            videoTime = System.currentTimeMillis()
            setData(videoBuffer, System.nanoTime(), rtpFrame.timeStamp)
            try {
                sendReport(
                    videoBuffer,
                    rtpFrame,
                    "Video",
                    videoPacketCount,
                    videoOctetCount,
                    isEnableLogs
                )
            } catch (e: IOException) {
                Log.e(TAG, "Error", e)
            }
        }
    }

    private fun updateAudio(rtpFrame: RtpFrame, isEnableLogs: Boolean) {
        audioPacketCount++
        audioOctetCount += rtpFrame.length
        audioBuffer.setLong(audioPacketCount.toLong(), 20, 24)
        audioBuffer.setLong(audioOctetCount.toLong(), 24, 28)
        if (System.currentTimeMillis() - audioTime >= interval) {
            audioTime = System.currentTimeMillis()
            setData(audioBuffer, System.nanoTime(), rtpFrame.timeStamp)
            try {
                sendReport(
                    audioBuffer,
                    rtpFrame,
                    "Audio",
                    audioPacketCount,
                    audioOctetCount,
                    isEnableLogs
                )
            } catch (e: IOException) {
                Log.e(TAG, "Error", e)
            }
        }
    }

    fun reset() {
        videoOctetCount = 0
        videoPacketCount = videoOctetCount
        audioOctetCount = 0
        audioPacketCount = audioOctetCount
        audioTime = 0
        videoTime = audioTime
        videoBuffer.setLong(videoPacketCount.toLong(), 20, 24)
        videoBuffer.setLong(videoOctetCount.toLong(), 24, 28)
        audioBuffer.setLong(audioPacketCount.toLong(), 20, 24)
        audioBuffer.setLong(audioOctetCount.toLong(), 24, 28)
    }

    abstract fun close()

    private fun setData(buffer: ByteArray, ntpts: Long, rtpts: Long) {
        val hb = ntpts / 1000000000
        val lb = (ntpts - hb * 1000000000) * 4294967296L / 1000000000
        buffer.setLong(hb, 8, 12)
        buffer.setLong(lb, 12, 16)
        buffer.setLong(rtpts, 16, 20)
    }
}